/*
 * @(#)Collocations.java	0.9 09/01/07
 *
 * Copyright 2007 Fabio Gasparetti. All rights reserved.
 */

package org.tabularium.text.nlp;

import java.util.*;
import java.io.*;
import org.tabularium.text.nlp.wordnet.PartOfSpeech;

/**
 * Default implementation where collocations are stored in one array of String
 * objets and the search is based on the binary search algoritm.
 * 
 * @author Fabio Gasparetti
 * @version 0.9, 09/01/07
 */
public class Collocations implements Serializable {

	protected String[] collocations = null;

	protected int[][] tagPatterns = {
			{ PartOfSpeech.ADJECTIVE, PartOfSpeech.NOUN },
			{ PartOfSpeech.NOUN, PartOfSpeech.NOUN },
			{ PartOfSpeech.ADJECTIVE, PartOfSpeech.ADJECTIVE, PartOfSpeech.NOUN },
			{ PartOfSpeech.ADJECTIVE, PartOfSpeech.NOUN, PartOfSpeech.NOUN },
			{ PartOfSpeech.NOUN, PartOfSpeech.ADJECTIVE, PartOfSpeech.NOUN },
			{ PartOfSpeech.NOUN, PartOfSpeech.NOUN, PartOfSpeech.NOUN },
			{ PartOfSpeech.NOUN, PartOfSpeech.PREPOSITION, PartOfSpeech.NOUN } };

	public Collocations() {
	}

	/**
	 * Read collocations from file.
	 * 
	 */
	public void load(String fn) throws IOException {
		Set colls = new TreeSet();
		FileReader reader = new FileReader(fn);
		BufferedReader bufReader = new BufferedReader(reader);
		String line;
		while ((line = bufReader.readLine()) != null) {
			if ((line.indexOf('_') >= 0) || (line.indexOf('-') >= 0)
					|| (line.indexOf(' ') >= 0)) {
				line = line.replace('_', ' ');
				line = line.replace('-', ' ');
				line = line.toLowerCase();
				colls.add(line);
			}
		}
		bufReader.close();
		reader.close();
		collocations = new String[colls.size()];
		Iterator iter = colls.iterator();
		int i = 0;
		while (iter.hasNext())
			collocations[i++] = (String) iter.next();
	}

	/**
	 * Returns true if the given text is a collocation.
	 * <p>
	 * The matching is:
	 * <ul>
	 * <li>case-insensitive
	 * <li>'-' and '_' characters are converted to spaces
	 * <li>leading, trailing and multiple whitespaces removed
	 * </ul>
	 */
	public boolean isCollocation(String text) {
		text = Collocations.removeUselessBlanks(text);
		text = text.replace('_', ' ');
		text = text.replace('-', ' ');
		text = text.toLowerCase();
		return Arrays.binarySearch(collocations, text) > 0;
	}

	/**
	 * Returns a list of Tag arrays objects, each one representing a collocation
	 * in the input Tag sequence representing a sentence.
	 * <p>
	 * The matching is performed keeping the punctuation and removing multiple
	 * white-spaces. See also isCollocation().
	 */
	public List findCollocations(Tag[] tags) {
		int i, j, k, h;
		boolean found;
		String s;
		Tag[] collocation;
		tags = filterTags(tags);
		ArrayList ret = new ArrayList();
		for (i = 0; i < tags.length; i++) {
			// dont start the search from an unknown tag
			if (tags[i].pos == PartOfSpeech.UNKNOWN)
				continue;
			// for each tag pattern...
			for (j = 0; j < tagPatterns.length; j++) {
				h = i;
				found = true;
				for (k = 0; k < tagPatterns[j].length;) {
					if (h == tags.length) {
						found = false;
						break;
					}
					if (tags[h].pos == PartOfSpeech.UNKNOWN) {
						h++;
						continue;
					}
					if (tags[h].pos == tagPatterns[j][k]) {
						h++;
						k++;
						continue;
					}
					found = false;
					break;
				}
				if (found) {
					s = new String();
					for (int l = i; l < h; l++)
						s = s.concat(tags[l].content);
					if (isCollocation(s)) {
						// System.out.println("collocations: "+s);
						collocation = new Tag[h - i];
						System.arraycopy(tags, i, collocation, 0, h - i);
						ret.add(collocation);
					}
				}
			}
		}
		return ret;
	}

	/**
	 * Returns a list of Tag arrays extracting the sentences from the given
	 * sequence of tags.
	 * 
	 * Basically, split the input sequence according to the punctuation.
	 */
	public static List retrieveSequences(Tag[] tags) {
		return null;
		// still have to find a way to implement it
		// ArrayList l = new ArrayList();
		// ArrayList l1 = new ArrayList();
		// long prevPosition = -1l;
		// for (int i = 0; i < tags.length; i++) {
		// if ((tags[i].pos == PartOfSpeech.NOUN)
		// || (tags[i].pos == PartOfSpeech.ADJECTIVE)
		// || (tags[i].pos == PartOfSpeech.PREPOSITION)) {
		// l1.add(tags[i]);
		// prevPosition = tags[i].start;
		// } else if (prevPosition == tags[i].start) {
		// // same content but different tag-type -> ignore
		// continue;
		// } else if (tags[i].pos == PartOfSpeech.UNKNOWN) {
		// if (indexOf(tags[i].content, "!\"\'()*,./:;<>?[\\]`{|}~") >= 0) {
		// // split the sentence
		// l.add(l1.toArray(new Tag[] {}));
		// l1.clear();
		// }
		// }
		// }
		// return l;
	}

	/**
	 * Filter a sequence of tags for
	 * 
	 * These are the steps:
	 * <ul>
	 * <li>remove any entity tag, e.g., PERSON, ORGANIZATION, etc.
	 * <li>keep nouns, adjectives and prepositions
	 * <li>keep puncutation and spaces (for collocatons such as "andrew w.
	 * mellon")
	 * </ul>
	 */
	protected static Tag[] filterTags(Tag[] tags) {
		ArrayList l = new ArrayList();
		Tag buffer = null;
		Tag lastTag = null;
		int i, j;
		for (i = 0; i < tags.length;) {
			if (lastTag != null) {
				if (lastTag.contains(tags[i]) || tags[i].contains(lastTag)) {
					i++;
					continue;
				}
			}

			// if we hold a tag, before moving on we add it
			if ((buffer != null) && (buffer.start < tags[i].start)) {
				lastTag = buffer;
				l.add(buffer);
				buffer = null;
				continue;
			}

			if ((tags[i].pos == PartOfSpeech.NOUN)
					|| (tags[i].pos == PartOfSpeech.ADJECTIVE)
					|| (tags[i].pos == PartOfSpeech.PREPOSITION)) {
				lastTag = tags[i];
				l.add(tags[i]);
				buffer = null;
				i++;
			} else {
				// if we have found a new tag shorter than the past one,
				// keep the last
				if (buffer == null)
					buffer = tags[i];
				i++;
			}
			/*
			 * else if (tags[i].pos == PartOfSpeech.UNKNOWN) { if
			 * (indexOf(tags[i].content, " -_!\"\'()*,./:;<>?[\\]`{|}~") >= 0) {
			 * l.add(tags[i]); prevPosition = tags[i].start; } }
			 */
		}
		return (Tag[]) l.toArray(new Tag[] {});
	}

	/**
	 * Returns the index of the first occurence in the first parameter of one of
	 * the characters in the second paramenter.
	 * 
	 * @param str
	 * @param chars
	 * @return
	 */
	protected static int indexOf(String str, String chars) {
		int i, j;
		for (i = 0; i < str.length(); i++)
			for (j = 0; j < chars.length(); j++)
				if (str.charAt(i) == chars.charAt(j))
					return i;
		return -1;
	}

	/**
	 * Returns a new string removing leading, trailing and multiple whitespaces
	 * from the given string.
	 */
	public static String removeUselessBlanks(String s) {
		if (s.length() == 0)
			return s;
		char[] chStr = s.toCharArray();
		int i, j = 0;
		char ch;
		boolean space = false;
		// eat leading blanks
		for (i = 0; i < chStr.length && chStr[i] == ' '; i++)
			;
		for (; i < chStr.length; i++) {
			ch = chStr[i];
			if (ch == ' ') {
				space = true;
			} else {
				if (space) {
					chStr[j++] = ' ';
					space = false;
				}
				chStr[j++] = ch;
			}
		}
		if (j < i)
			chStr[j] = 0;
		return new String(chStr, 0, j);
	}

	// debug
	public static void main(String args[]) throws Exception {
		String s = new String(" x x");
		s = Collocations.removeUselessBlanks(s);
		System.out.println('-' + s + '-');
		Tag[] tags = new Tag[10];
		tags[0] = new Tag();
		tags[0].pos = PartOfSpeech.ADJECTIVE;
		tags[1] = new Tag();
		tags[1].pos = PartOfSpeech.NOUN;
		tags[2] = new Tag();
		tags[2].pos = PartOfSpeech.NOUN;
		tags[3] = new Tag();
		tags[3].pos = PartOfSpeech.ADJECTIVE;
		tags[4] = new Tag();
		tags[4].pos = PartOfSpeech.ADJECTIVE;
		tags[5] = new Tag();
		tags[5].pos = PartOfSpeech.NOUN;
		tags[6] = new Tag();
		tags[6].pos = PartOfSpeech.ADJECTIVE;
		tags[7] = new Tag();
		tags[7].pos = PartOfSpeech.NOUN;
		tags[8] = new Tag();
		tags[8].pos = PartOfSpeech.ADJECTIVE;
		tags[9] = new Tag();
		tags[9].pos = PartOfSpeech.NOUN;

		try {
			Collocations wnc = new Collocations();
			// wnc.load(null);
			wnc.findCollocations(tags);

			// for (int i = 0; i < wnc.collocations.length; i++)
			// System.out.println(wnc.collocations[i]);

		} catch (Exception ex) {
			System.out.println(ex.toString());
			ex.printStackTrace();
		}
	}
}
